

import java.io.*;

import javax.sound.sampled.*;

public class SoundManager extends ThreadPool implements Runnable{
	
	private AudioFormat playbackFormat;
	private ThreadLocal<SourceDataLine> localLine;
	private ThreadLocal<byte[]> localBuffer;
	private Object pausedLock;
	private boolean paused;
	
	public SoundManager(AudioFormat playbackFormat){
		this(playbackFormat, getMaxSounds(playbackFormat));
	}
	
	public SoundManager(AudioFormat playbackFormat, int maxSounds){
		super(maxSounds);
		this.playbackFormat = playbackFormat;
		localLine = new ThreadLocal<SourceDataLine>();
		localBuffer = new ThreadLocal<byte[]>();
		pausedLock = new Object();
		synchronized(this){
			notifyAll();
		}
	}
	
	public static int getMaxSounds(AudioFormat playbackFormat){
		DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
		Mixer mixer = AudioSystem.getMixer(null);
		return mixer.getMaxLines(lineInfo);
	}
	
	protected void cleanUp(){
		setPaused(false);
		Mixer mixer = AudioSystem.getMixer(null);
		if(mixer.isOpen()){
			mixer.close();
		}
	}
	
	public void close(){
		cleanUp();
		super.close();
	}
	
	public void join(){
		cleanUp();
		super.join();
	}
	
	public void setPaused(boolean paused){
		if (this.paused != paused){
			synchronized (pausedLock){
				this.paused = paused;
				if (!paused){
					pausedLock.notifyAll();
				}
			}
		}
	}
	
	public boolean isPaused(){
		return paused;
	}
	
	public Sound getSound(String filename){
		return getSound(getAudioInputStream(filename));
	}
	
	public Sound getSound(AudioInputStream audioStream){
		if (audioStream == null){
			return null;
		}
		
		int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());
		
		byte[] samples = new byte[length];
		DataInputStream inStream = new DataInputStream(audioStream);
		try{
			inStream.readFully(samples);
		}
		catch(IOException ex){
			ex.printStackTrace();
		}
		
		return new Sound(samples);
	}
	
	public AudioInputStream getAudioInputStream(String filename){		
		try{
			AudioInputStream source = AudioSystem.getAudioInputStream(new File(filename));
			return AudioSystem.getAudioInputStream(playbackFormat, source);
		}
		catch(UnsupportedAudioFileException ex){
			ex.printStackTrace();
		}
		catch(IOException ex){
			ex.printStackTrace();
		}
		catch(IllegalArgumentException ex){
			ex.printStackTrace();
		}
		
		return null;
	}
	
	public InputStream play(Sound sound){
		return play(sound, false);
	}
	
	public InputStream play(Sound sound, boolean loop){
		InputStream inStream;
		if(sound != null){
			if(loop){
				inStream = new LoopingByteInputStream(sound.getSamples());
			}
			else{
				inStream = new ByteArrayInputStream(sound.getSamples());
			}
			
			return play(inStream);
		}
		
		return null;
	}
	
	public InputStream play(InputStream inStream){
		if(inStream != null){
			runTask(new SoundPlayer(inStream));
		}
		return inStream;
	}
	
	protected void threadStarted(){
		synchronized(this){
			try{
				wait();
			}
			catch(InterruptedException ex){}
		}
		
		int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);
		
		SourceDataLine line;
		DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
		
		try{
			line = (SourceDataLine) AudioSystem.getLine(lineInfo);
			line.open(playbackFormat, bufferSize);
		}
		catch(LineUnavailableException|IllegalStateException ex){
			Thread.currentThread().interrupt();
			return;
		}
		
		line.start();
		
		byte[] buffer = new byte[bufferSize];
		
		localLine.set(line);
		localBuffer.set(buffer);
	}
	
	protected void threadStopped(){
		SourceDataLine line = (SourceDataLine) localLine.get();
		if(line != null){
			line.drain();
			line.close();
		}
	}
	
	protected class SoundPlayer implements Runnable {

        private InputStream source;

        public SoundPlayer(InputStream source) {
            this.source = source;
        }

        public void run() {
            // get line and buffer from ThreadLocals
            SourceDataLine line = (SourceDataLine)localLine.get();
            byte[] buffer = (byte[])localBuffer.get();
            if (line == null || buffer == null) {
                // the line is unavailable
                return;
            }

            // copy data to the line
            try {
                int numBytesRead = 0;
                while (numBytesRead != -1) {
                    // if paused, wait until unpaused
                    synchronized (pausedLock) {
                        if (paused) {
                            try {
                                pausedLock.wait();
                            }
                            catch (InterruptedException ex) {
                                return;
                            }
                        }
                    }
                    // copy data
                    numBytesRead =
                        source.read(buffer, 0, buffer.length);
                    if (numBytesRead != -1) {
                        line.write(buffer, 0, numBytesRead);
                    }
                }
            }
            catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
	}
}

